package org.hackystat.ui.orb.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;

import org.hackystat.ui.orb.client.common.RPCException;

/**
 * Provides access to the Project Viewer's properties file for each sensor, and provides
 * reasonable default values when properties file lacks property values. <p>
 *
 * The creation of a Properties instance requires a valid sensor properties file. The sensor 
 * properties file is stored in userhome/.hackystat/projectviewer/projectviewer.properties.
 * It is a user-maintained file of key=value pairs. <p>
 * 
 * All of the properties in projectviewer.properties are added to the System property instance
 * after being read from the file. This enables other tools that interoperate with Hackystat to 
 * obtain settings from the System instance and/or environments to configure System property 
 * settings (such as for SSL) simply by adding entries to the properties file. 
 *
 * @author David J. Nickles, Philip M. Johnson, Aaron A. Kagawa
 */
public class Properties implements Serializable {

  /** Serializable ID. */
  private static final long serialVersionUID = 2336433135397294917L;
  /** Hackystat Sensorbase host. */
  private static final String HACKYSTAT_SENSORBASE_HOST = "HACKYSTAT_SENSORBASE_HOST";
  /** Hackystat Analysis DailtProjectData host. */
  private static final String HACKYSTAT_DAILYPROJECTDATA_HOST = "HACKYSTAT_DAILYPROJECTDATA_HOST";
  /** Hackystat Analysis Telemetry host. */
  private static final String HACKYSTAT_TELEMETRY_HOST = "HACKYSTAT_TELEMETRY_HOST";
  /** User's email. */
  private static final String EMAIL_KEY = "HACKYSTAT_EMAIL";
  /** User's password. */
  private static final String PASSWORD_KEY = "HACKYSTAT_PASSWORD";
  /** Polling time interval.  */
  private static final String RELATIVE_PATH = 
    "/.hackystat/projectviewer/projectviewer.properties";

  /** The standard location of the sensor properties file. */
  private transient File file;

  /** Whether the properties file exists and was successfully read. */
  private boolean fileAvailable = false;
  /** The internal properties object. */
  private java.util.Properties props = new java.util.Properties();

  /**
   * Initializes based on the user's projectviewer.properties file.
   * @throws RPCException If the SDBProperties instance cannot be instantiated. 
   */
  public Properties() throws RPCException {
    this(new File(System.getProperty("user.home") + RELATIVE_PATH));
  }

  /**
   * Provides access to Hackystat Sensor settings by reading specified sensor properties file. 
   * @param sensorFile The sensor file to read.
   * @throws RPCException If the Properties instance cannot be instantiated. 
   */
  public Properties(File sensorFile) throws RPCException {
    // validation of the sensor file
    if (sensorFile == null) {
      throw new RPCException("Invalid projectviewer properties file");
    }
    if (!sensorFile.exists()) {
      throw new RPCException("projectviewer properties file does not exist at "
          + sensorFile.getAbsolutePath());
    }
    if (!sensorFile.isFile()) {
      throw new RPCException("projectviewer properties must be a file.");
    }
    if (!sensorFile.canRead()) {
      throw new RPCException("Unable to read the projectviewer properties file at "
          + sensorFile.getAbsolutePath());
    }

    // the sensor file should be valid at this point, whether there is anything is not know yet.
    this.file = sensorFile;
    FileInputStream fileStream = null;
    try {
      fileStream = new FileInputStream(this.file);
      this.props.load(fileStream);
      if (this.props.size() > 0) {
        this.fileAvailable = true;
        // Add the current set of hackystat properties to the System property object.
        this.addToSystemProperties(this.props);
      }
    }
    catch (FileNotFoundException e) {  
      throw new RPCException("TV properties file does not exist at " // NOPMD cannot chain here
          + sensorFile.getAbsolutePath());
    }
    catch (IOException e) {  //NOPMD cannot chain serializable exceptions. 
      throw new RPCException("Unable to read the TV properties file at " // NOPMD cannot chain
          + sensorFile.getAbsolutePath());
    }
    finally {
      try {
        fileStream.close();
      }
      catch (Exception e) {
        System.err.println("Error closing stream: " + e);
      }
    }
  }

  /**
   * Creates a "minimal" sensor properties file usable for test case purposes. Needed by SensorShell
   * which must be passed a Properties object containing a host, email, and password.
   * @param sensorbase The hackystat sensorbase host.
   * @param telemetry The hackystat telemetry host.
   * @param email The user's email.
   * @param password The user's password.
   */
  public Properties(String sensorbase, String telemetry, String email, String password) {
    this.props.setProperty(Properties.HACKYSTAT_SENSORBASE_HOST, sensorbase);
    this.props.setProperty(Properties.HACKYSTAT_TELEMETRY_HOST, telemetry);
    this.props.setProperty(Properties.EMAIL_KEY, email);
    this.props.setProperty(Properties.PASSWORD_KEY, password);
    // Add the current set of hackystat properties to the System property object.
    this.addToSystemProperties(this.props);
    this.fileAvailable = false;
  }
  
  /**
   * Returns the directory in which the sensor.properties file is located (if it exists). This is
   * normally the .hackystat directory. If this Properties instance was created without a
   * sensor.properties file, or if for some other reason the sensor.properties file cannot be found,
   * then this method returns null.
   *
   * @return A File instance indicating a directory, or null.
   */
  public File getPropertiesDir() {
    if ((this.file != null) && (this.file.exists())) {
      return this.file.getParentFile();
    }
    else {
      return null;
    }
  }
  
  /**
   * Returns the trimmed property value associated with the property key in this 
   * v8.sensor.properties file. In most cases, it is recommended that clients use the access 
   * methods like <code>getHackystatHost()</code>, because these methods provide default values.
   * @param key The parameter key.
   * @return The trimmed property value associated with this property key, or null if not found.
   */
  public String getProperty(String key) {
    String value = this.props.getProperty(key);
    if (value != null) {
      value = value.trim();
    }
    return value;
  }

  /**
   * Returns the hackystat sensorbase host. Defaults to http://localhost/.
   * @return The hackystat sensorbase host.
   */
  public String getSensorbaseHost() {
    String host = this.props.getProperty(HACKYSTAT_SENSORBASE_HOST, 
      "http://localhost/").trim(); 
    if (!host.endsWith("/")) {
      host = host + "/";
    }
    return host;
  }

  /**
   * Returns the hackystat dailyprojectdata host. Defaults to http://localhost/.
   * @return The hackystat dailyprojectdata host.
   */
  public String getDailyProjectDataHost() {
    String host = this.props.getProperty(HACKYSTAT_DAILYPROJECTDATA_HOST, 
      "http://localhost/").trim(); 
    if (!host.endsWith("/")) {
      host = host + "/";
    }
    return host;
  }

  /**
   * Returns the hackystat telemetry host. Defaults to http://localhost/.
   * @return The hackystat telemetry host.
   */
  public String getTelemetryHost() {
    String host = this.props.getProperty(HACKYSTAT_TELEMETRY_HOST, 
      "http://localhost/").trim(); 
    if (!host.endsWith("/")) {
      host = host + "/";
    }
    return host;
  }


  /**
   * Returns the absolute path to the properties file, or the empty string if the file is not
   * available.
   * @return The absolutePath value or the empty string.
   */
  public String getAbsolutePath() {
    return (this.file == null) ? "" : this.file.getAbsolutePath();
  }


  /**
   * Returns the password for this user. Defaults to "ChangeThis"
   * @return The user password.
   */
  public String getPassword() {
    return this.props.getProperty(PASSWORD_KEY, "ChangeThis").trim();
  }

  /**
   * Returns the email for this user. Defaults to "ChangeThis@changethis.com"
   * @return The user email.
   */
  public String getEmail() {
    return this.props.getProperty(EMAIL_KEY, "ChangeThis@changethis.com").trim();
  }

  /**
   * Returns true if the sensor properties file was found and read successfully.
   * @return True if file was found and readable.
   */
  public boolean isFileAvailable() {
    return this.fileAvailable;
  }

  /**
   * Updates the System properties object with the contents of the passed Hackystat
   * properties instance.  This method is declared static so that it can be invoked
   * from both Properties and ServerProperties.  The hackystat properties added from
   * Properties were found in sensor.properties. The hackystat properties added from
   * ServerProperties were found in hackystat.server.properties.  
   * This method is package-private for access by Properties.
   * @param hackyProperties A Properties instance containing hackystat properties.
   */
  private void addToSystemProperties(java.util.Properties hackyProperties) {
    java.util.Properties systemProperties = System.getProperties();
    systemProperties.putAll(hackyProperties);
    System.setProperties(systemProperties);
  }
}

